# Copyright (c) 2019 Presto Labs Pte. Ltd.
# Author: xguo

import collections
import enum
import json
import logging
import os
import pathlib
import sys
import yaml

import dateutil.parser

from coin.base.config import Config
from coin.exchange.base.order_gateway_config import convert_og_config_from_dict
from coin.exchange.shared.kr_rest.product import SharedProduct
from coin.experimental.xguo.fruit.util.convert_symbol_to_product import convert_symbol_to_product
from coin.proto.coin_order_checker_pb2 import AccountCheckerConfig
from coin.strategy.accounting.pnl_balance_printer.util import reserve_as_proto
from coin.strategy.util.request_util import convert_strategy_request_from_dict
from coin.support.accounting.checker.config_util import convert_checker_config_from_dict

from coin.proto.coin_executor_pb2 import (
    PassiveExecutorConfig,
    AggressiveExecutorConfig,
)
from coin.strategy.executor.passive.util import (
    convert_executor_config_from_dict as convert_mm_executor_config_from_dict,)
from coin.strategy.executor.aggressive.util import (
    convert_executor_config_from_dict as convert_agg_executor_config_from_dict,)
from coin.strategy.executor.passive.factory import (
    construct_mm_executor,
    construct_agg_executor,
)
from coin.strategy.mm.feed import (
    SubscriptionRequest,
    get_flow_book_builder_cls,
)


def get_product_type_from_subreq(subreq):
  product_type = get_flow_book_builder_cls(subreq).ProductType
  return product_type


FeedSubsystemConfig = collections.namedtuple('FeedSubsystemConfig', [
    'subreq',
    'products',
])

OrderSubsystemConfig = collections.namedtuple('OrderSubsystemConfig',
                                              [
                                                  'subreq',
                                                  'currencies',
                                                  'products',
                                                  'og_config',
                                                  'og_params',
                                              ])


class ExecutorType(enum.Enum):
  PASSIVE = 'Passive'
  AGGRESSIVE = 'Aggressive'


def convert_executor_config_from_dict(config):
  executor_type = ExecutorType(config['executor_type'])
  if executor_type == ExecutorType.PASSIVE:
    symbol = config['symbol']
    config_proto = convert_mm_executor_config_from_dict(symbol, config)
  elif executor_type == ExecutorType.AGGRESSIVE:
    symbol = config['symbol']
    config_proto = convert_agg_executor_config_from_dict(symbol, config)
  else:
    raise NotImplementedError()
  return config_proto


def construct_executor(subreq, config_proto, og, logger):
  if isinstance(config_proto, PassiveExecutorConfig):
    executor = construct_mm_executor(subreq, config_proto, og, logger)
  elif isinstance(config_proto, AggressiveExecutorConfig):
    executor = construct_agg_executor(subreq, config_proto, og, logger)
  else:
    raise NotImplementedError()
  return executor


class BaseStrategyConfig(object):
  def __init__(self, config_dict: dict):
    self._config_dict = config_dict
    self._feed_subsystem_config = None
    self._order_subsystem_config = None
    self._executor_config = None
    self._account_checker_config = None
    self._sim_config = None
    self._strategy_core_config = None
    self._logger = logging.getLogger(__name__)

    self.parse_all()

  def __str__(self):
    return json.dumps(self._config_dict, indent=2, default=str)

  @property
  def feed_subsystem_config(self):
    return self._feed_subsystem_config

  @property
  def order_subsystem_config(self):
    return self._order_subsystem_config

  @property
  def account_checker_config(self):
    return self._account_checker_config

  @property
  def executor_config(self):
    return self._executor_config

  @property
  def sim_config(self):
    return self._sim_config

  @property
  def strategy_core_config(self):
    return self._strategy_core_config

  def parse_all(self):
    # parse
    self._parse_feed_subsystem_config()
    self._parse_sim_config()
    self._parse_order_subsystem_config()
    self._parse_account_checker_config()
    self._parse_strategy_core_config()
    self._parse_executor_config()

  def _parse_strategy_core_config(self):
    config = self._config_dict.get('strategy_core_config')
    if config is None:
      return

    reserve_dict = config.get('reserve')
    subreq_list = []
    if reserve_dict is not None:
      config['reserve_proto'] = {}
      for subreq_str, reserve_info in reserve_dict.items():
        subreq = SubscriptionRequest.from_str(subreq_str)
        subreq_list.append(subreq)
        reserve_proto = reserve_as_proto(subreq, reserve_info)
        config['reserve_proto'][subreq] = reserve_proto
    if 'strategy_request' in config:
      config['strategy_request'] = convert_strategy_request_from_dict(config['strategy_request'])
    config['subreq_list'] = subreq_list
    self._strategy_core_config = config

  def _parse_feed_subsystem_config(self):
    config = self._config_dict.get('feed_subsystem_config')
    if config is None:
      return

    self._feed_subsystem_config = {}
    for subreq_str, product_list in config.items():
      subreq = SubscriptionRequest.from_str(subreq_str)
      product_type = get_product_type_from_subreq(subreq)
      config = FeedSubsystemConfig(subreq=subreq, products=[])
      self._feed_subsystem_config[subreq] = config
      for product_str in product_list:
        product = product_type.FromStr(product_str)
        config.products.append(product)

  def _parse_sim_config(self):
    config = self._config_dict.get('sim_config')
    if config is None:
      return

    self._sim_config = config.copy()
    sim_config = self._sim_config
    sim_config['from_ts'] = dateutil.parser.parse(str(config['from_ts']))
    sim_config['to_ts'] = dateutil.parser.parse(str(config['to_ts']))
    sim_config['trading_date'] = sim_config['from_ts']
    if 'root_dir' in config:
      sim_config['root_dir'] = os.path.expanduser(config['root_dir'])

  def _parse_order_subsystem_config(self):
    config = self._config_dict.get('order_subsystem_config')
    if config is None:
      return

    self._order_subsystem_config = {}
    for subreq_str, og_params_dict in config.items():
      subreq = SubscriptionRequest.from_str(subreq_str)
      product_type = get_product_type_from_subreq(subreq)
      currency_type = product_type.NativeCurrencyType
      key_file = og_params_dict.get('key_file', 'no_key_file')
      try:
        trade_key_config = Config.from_config_filename(key_file)
        key_file = trade_key_config.key_file
      except ValueError:
        self._logger.error('key_file does not exist!')
        key_file = 'no_key_file'
      currencies = self._parse_currencies(currency_type, og_params_dict['currencies'])
      products = self._parse_products(product_type, og_params_dict['products'])
      og_params = convert_og_config_from_dict(subreq, og_params_dict.get('og_params', {}))
      config = OrderSubsystemConfig(subreq=subreq,
                                    currencies=currencies,
                                    products=products,
                                    og_config=key_file,
                                    og_params=og_params)
      self._order_subsystem_config[subreq] = config

  @staticmethod
  def _parse_currencies(currency_type, currency_list):
    currencies = []
    for currency_str in currency_list:
      currency = currency_type.FromStr(currency_str)
      currencies.append(currency)
    return currencies

  @staticmethod
  def _parse_products(product_type, product_list):
    products = []
    for product_str in product_list:
      product = product_type.FromStr(product_str)
      products.append(product)
    return products

  def _parse_account_checker_config(self):
    config = self._config_dict.get('account_checker')
    if config is None:
      return

    config_keys = {
        'reserve',
        'lot_size',
        'stack_size',
        'ratio_min_balance',
        'ratio_max_balance',
        'ratio_min_hold_balance',
    }

    balance_check_config_list = []
    for currency, config in config['account_checker'].items():
      config = {**config}
      if 'lot_size' not in config:
        for product in config['products']:
          base = SharedProduct.FromStrNativeProduct(product).base.currency
          if base == currency:
            config['lot_size'] = config['products'][product]['lot_size']
            break

      assert config_keys == set(config.keys()), config
      reserve = config['reserve']
      config['currency'] = currency
      config['min_balance'] = reserve * config['ratio_min_balance']
      config['max_balance'] = reserve * config['ratio_max_balance']
      config['min_available_balance'] = 0.0
      config['max_available_balance'] = config['lot_size'] * config['stack_size']
      config['min_hold_balance'] = reserve * config['ratio_min_hold_balance']
      config['max_hold_balance'] = reserve * config['ratio_max_balance']
      currency_checker_config = convert_checker_config_from_dict(config)
      balance_check_config_list.append(currency_checker_config)

    config_proto = AccountCheckerConfig(each_balance_check_config=balance_check_config_list)
    self._account_checker_config = config_proto

  def _parse_executor_config(self):
    config_dict = self._config_dict.get('executor_config')
    if config_dict is None:
      return

    self._executor_config = {}
    for symbol, config in config_dict.items():
      product = convert_symbol_to_product(symbol)
      config.setdefault('symbol', product.symbol)
      self._executor_config[product] = {
          'subreq': config['subreq'],
          'proto_config': convert_executor_config_from_dict(config),
      }

  @classmethod
  def from_file(cls, config_file):
    config_file = pathlib.Path(config_file)
    assert config_file.is_file(), config_file
    with config_file.open() as infile:
      config_dict = yaml.load(infile)
    return cls(config_dict)


def main():
  if len(sys.argv) == 1:
    config = BaseStrategyConfig.from_file('config.yaml')
  else:
    config = BaseStrategyConfig.from_file(sys.argv[1])

  print(config.feed_subsystem_config)
  print(config.order_subsystem_config)
  print(config)


if __name__ == '__main__':
  main()
